﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Magiq.Query;
using Magiq.Support;

namespace Magiq.Insert
{
    public class InsertSource<TNew>
    {
        public LambdaExpression InsertExpression { get; private set; }

        protected LambdaExpression SourceCollectionExpression { get; private set; }

        public Expression SourceExpression { get; private set; }
        
        protected Type InsertParameterType { get; private set; }
        

        public IQueryable<TNew> Queryable { get; private set; }

        public Type SourceCollectionItemType
        {
            get { return SourceCollectionExpression.Parameters.First().Type.GetGenericArguments().Single(); }
        }

        public IQueryable ApplyQueryTo(IQueryable queryable)
        {
            var provider = (IInsertSourceQueryProvider)Activator.CreateInstance(typeof(RepositoryQueryableProvider<,>).MakeGenericType(SourceExpression.Type, InsertParameterType), SourceCollectionExpression);
            return provider.ApplyQueryTo(queryable);
        }

        public InsertSource(IQueryable<TNew> source) 
        {
            Queryable = source;
            var call = source.Expression as MethodCallExpression;
            var start = call == null ? source.Expression : call.Arguments[0];

            InsertExpression = ExpressionSelectSplit.From(call).Select;
            ProcessSourceExpression(start);
            ProcessSourceCollectionExpression(start);
        }
        
        private void ProcessSourceCollectionExpression(Expression start)
        {
            var sourceParameter = Expression.Parameter(SourceExpression.Type, "p");
            InsertParameterType = InsertExpression.Parameters.First().Type;
            var rest = new ExpressionChanger(SourceExpression, sourceParameter).Visit(start);

            rest = rest.WrapInEnumerable();
            SourceCollectionExpression = Expression.Lambda(rest, sourceParameter);
        }

        private void ProcessSourceExpression(Expression start)
        {
            var current = start;
            var expressions = new List<Expression> {current};

            while (current != null)
            {
                if (current is MemberExpression)
                {
                    current = ((MemberExpression) current).Expression;
                    expressions.Add(current);
                }
                else if (current is MethodCallExpression)
                {
                    var arguments = ((MethodCallExpression) current).Arguments;
                    if (arguments.Count > 0)
                    {
                        current = ((MethodCallExpression) current).Arguments[0];
                        expressions.Add(current);
                    }
                    else
                    {
                        current = null;
                    }
                }
                else
                {
                    current = null;
                }
            }

            SourceExpression = expressions.Last(x=>!x.Type.IsValueType); //I know, this is awful. TODO: Refactor this!!
        }

        public IInsert CreateInsert<TDestination>(IQueryable<TDestination> destination)
        {
            return (IInsert)
                   Activator.CreateInstance(
                       typeof(Insert<,,>).MakeGenericType(InsertParameterType, typeof(TDestination), typeof(TNew)),
                       InsertExpression, this, destination);
        }

        public IInsert CreateBulkInsert<TDestination>(IQueryable<TDestination> destination)
        {
            return (IInsert)
                   Activator.CreateInstance(
                       typeof(Insert<,,>).MakeGenericType(InsertParameterType, typeof(TDestination), typeof(TNew)),
                       InsertExpression, this, destination);
        }
    }
}